import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Between, LessThan, LessThanOrEqual, Like, MoreThan, MoreThanOrEqual, Repository } from 'typeorm';
import {
  UserAdminIndexDto,
  UserAdminUpdateDto,
  UserCreateDto,
  UserLoginDto,
  UserMobileLoginWxDto,
  UserMobilePreLoginWxDto,
  UserOpenApiLoginCheckDto,
  UserResetPasswordDto,
  UserUpdateDto,
  UserUpdatePasswordDto,
  UserVerifyRegisterDto,
} from './user.dto';
import { UserModel } from './user.model';
import { VcodeService } from '@/module/vcode/vcode.service';
// import { TencentCaptchaService } from '@/module/tencent-captcha/tencent-captcha.service';
import { RedisService } from '@liaoliaots/nestjs-redis';
import { Redis } from 'ioredis';
import { UserLoginLogModel } from '@/module/user-login-log/user-login-log.model';
import dayjs from 'dayjs';
import { createHmac } from "crypto";
import { nanoid } from 'nanoid';
import { RedisNamespace } from "@/common/config/config.interface";
import { BizFailedException } from "@/common/biz-failed/biz-failed.exception";
import { BizFailedCodeEnum } from "@/common/biz-failed/biz-failed.enum";
import { UserAuthGuard } from "@/common/auth/user-auth.guard";
// import { TencentCaptchaVerifyType } from "@/module/tencent-captcha/tencent-captcha.enum";
import { BooleanNumber, SendMineVcodeType, SendVcodeType, UserTitleCertification } from "common/utils/data";
import { omitUndefined } from "@/common/utils/utils";
import { WxService } from "@/module/wx/wx.service";

@Injectable()
export class UserService {
  private userLoginRedisClient: Redis;
  constructor(
    @InjectRepository(UserModel)
    private readonly userModelRepository: Repository<UserModel>,
    @InjectRepository(UserLoginLogModel)
    private readonly userLoginLogModelRepository: Repository<UserLoginLogModel>,
    private readonly vcodeService: VcodeService,
    // private readonly tencentCaptchaService: TencentCaptchaService,
    private readonly redisService: RedisService,
    private readonly wxService: WxService,
  ) {
    this.userLoginRedisClient = this.redisService.getClient(
      RedisNamespace.user_login,
    );
  }

  async verifyRegister(
    query: UserVerifyRegisterDto,
  ): Promise<boolean> {
    if (query.mobile) {
      const count = await this.userModelRepository.count({
        where: { mobile: query.mobile },
      });
      return Boolean(count)
    }
    if (query.name) {
      const count = await this.userModelRepository.count({
        where: { name: query.name },
      });
      return Boolean(count)
    }
    return false;
  }

  async create(body: UserCreateDto): Promise<void> {
    await this.vcodeService.verify(body.vcodeKey, body.vcode, SendVcodeType.USER_REGISTER);
    const userModel = new UserModel();
    try {
      await this.userModelRepository.save(Object.assign(userModel,{
        mobile: body.mobile,
        name: body.name,
        password: this.sha256Password(body.password),
      }));
    } catch (e: any) {
      if (e.constraint === 'USER.name') {
        throw new BizFailedException(BizFailedCodeEnum.USER_NAME_EXISTS);
      }
      if (e.constraint === 'USER.mobile') {
        throw new BizFailedException(BizFailedCodeEnum.USER_MOBILE_EXISTS);
      }
      throw e;
    }
  }

  async login(body: UserLoginDto): Promise<string> {
   /* await this.tencentCaptchaService.verify({
      type: TencentCaptchaVerifyType.LOGIN_REGISTER,
      Ticket: body.ticket,
      Randstr: body.randstr,
      UserIP: ip,
    });*/
    let user;
    if(body.vcode){
      await this.vcodeService.verify(body.vcodeKey, body.vcode, SendVcodeType.USER_LOGIN);
      user = await this.userModelRepository.findOne({
        where: {
          mobile: body.mobile,
        },
        select: ['uuid', 'name', 'mobile', 'mobile', 'status', 'userType'],
      });
    }else if(body.password){
      user = await this.userModelRepository.findOne({
        where: {
          mobile: body.mobile,
          password: this.sha256Password(body.password),
        },
        select: ['uuid', 'name', 'mobile', 'mobile', 'status', 'userType'],
      });
    }
    if (!user) {
      throw new BizFailedException(BizFailedCodeEnum.USER_LOGIN_WRONG);
    }
    if (!user.status) {
      throw new BizFailedException(BizFailedCodeEnum.USER_DISABLED);
    }
    const token = await this.genTokenByUser(user);
    return token!;
  }

  async loginWx(body: UserMobileLoginWxDto){
    const { openid } = await this.wxService.userLogin(body.jsCode);
    const user = await this.userModelRepository.findOne({
      where: [
        {
          mobile: body.nameOrMobile,
          password: this.sha256Password(body.password),
        },
        {
          name: body.nameOrMobile,
          password: this.sha256Password(body.password),
        },
      ],
    });
    if (!user) {
      throw new BizFailedException(BizFailedCodeEnum.USER_LOGIN_WRONG);
    }
    if (!user.status) {
      throw new BizFailedException(BizFailedCodeEnum.USER_DISABLED);
    }
    const token = await this.genTokenByUser(user);
    await this.userModelRepository.update({
      uuid: user!.uuid,
    }, {
      wxId: openid,
    });
    return token!;
  }

  async preLoginWx(body: UserMobilePreLoginWxDto){
    if(body.token){
      const userStr = await this.userLoginRedisClient.get(body.token);
      if(userStr){
        // 续一波期
        this.userLoginRedisClient.expire(body.token, UserAuthGuard.authExpireSecond);
        return {
          token: body.token,
        }
      }
    }
    const { openid } = await this.wxService.userLogin(body.jsCode);
    const user = await this.userModelRepository.findOne({
      where: {
        wxId: openid
      }
    });
    if (!user || !user.status) {
      return {
        token: null
      }
    }
    const token = await this.genTokenByUser(user);
    return {
      token
    }
  }
  
  private async genTokenByUser(
    user: UserModel,
  ){
    const token = nanoid();
    await Promise.all([
      this.userLoginRedisClient.set(
        token,
        JSON.stringify(user),
        'EX',
        UserAuthGuard.authExpireSecond,
      ),
      this.userLoginLogModelRepository.save({
        token,
        userUuid: user.uuid,
      }),
    ]);
    return token;
  }

  async logout(token: string): Promise<void> {
    await this.userLoginRedisClient.del(token);
  }

  async showMine(uuid: string) {
    return this.userModelRepository.findOne({
      where: {
        uuid,
      },
      select: [
        'uuid',
        'name',
        'mobile',
        'mobile',
        'title',
        'userType',
        'avatar',
        'titleCertification',
        'signature',
        'createTime'
      ],
    });
  }

  async updateMine(
    body: UserUpdateDto,
    uuid: string,
  ): Promise<void> {
    const updateParam: UserUpdateDto & {
      titleCertification?: string;
    } = body;
    if (body.title) {
      // 如果用户自己修改了title，那么title认证也没了，需要重新认证
      updateParam.titleCertification = UserTitleCertification.NORMAL;
    }
    await this.userModelRepository.update(
      {
        uuid,
      },
      {
        ...omitUndefined(updateParam),
      }
    );
  }

  async updateMinePassword(
    body: UserUpdatePasswordDto,
    uuid: string,
  ): Promise<void> {
    await this.vcodeService.verify(body.vcodeKey, body.vcode, SendMineVcodeType.USER_RESET_MY_PWD);
    await this.userModelRepository.update(
      {
        uuid,
      },
      {
        password: this.sha256Password(body.password),
      },
    );
    await this.kick(uuid);

  }

  async resetPassword(
    body: UserResetPasswordDto,
  ): Promise<void> {
    await this.vcodeService.verify(body.vcodeKey, body.vcode, SendVcodeType.USER_RESET_PWD);
    const user = await this.userModelRepository.findOne({
      where: {
        mobile:body.mobile
      }
    });
    if(!user){
      throw new BizFailedException(BizFailedCodeEnum.USER_MOBILE_NOT_EXISTS)
    }
    await this.userModelRepository.update(
      {
        uuid: user.uuid,
      },
      {
        password: this.sha256Password(body.password),
      },
    );
    await this.kick(user.uuid);
  }

  async adminIndex(query: UserAdminIndexDto) {
    interface IFilter {
      name?: any;
      mobile?: any;
      createTime?: any
    }
    const filter: IFilter = {};

    if (query.name) {
      filter.name = Like(`%${query.name}%`);
    }

    if (query.mobile) {
      filter.mobile = Like(`%${query.mobile}%`);
    }

    if(query.createTimeStart || query.createTimeEnd){
      const start = dayjs(query.createTimeStart).format('YYYY-MM-DD');
      const end = dayjs(query.createTimeEnd).format('YYYY-MM-DD');
      if(query.createTimeStart && query.createTimeEnd){
        filter.createTime = Between(
          start,
          end,
        );
      }else{
        if (query.createTimeStart) {
          filter.createTime = MoreThanOrEqual(start);
        }

        if (query.createTimeEnd) {
          filter.createTime = LessThanOrEqual(end);
        }
      }
    }

    return this.userModelRepository.findAndCount({
      where: filter,
      skip: query.getOffset(),
      take: query.getPageSize(),
      select: [
        'id',
        'uuid',
        'name',
        'mobile',
        'mobile',
        'status',
        'avatar',
        'title',
        'titleCertification',
        'signature',
        'createTime',
        'updateTime',
      ],
      order: {
        id: 'DESC',
      },
    });
  }

  async adminUpdate(body: UserAdminUpdateDto, uuid: string): Promise<void> {
    if (!body.status) {
      await this.kick(uuid);
    }
    await this.userModelRepository.update(
      {
        uuid,
      },
      omitUndefined(body),
    );
  }

  // 把该用户三个月内的token都踢下线
  private async kick(uuid: string){
    const res = await this.userLoginLogModelRepository.find({
      where: {
        userUuid: uuid,
        createTime: MoreThan(dayjs().subtract(3,'month').format('YYYY-MM-DD') as unknown as number),
      },
    });
    const delTokensPromises = res.map((item) => {
      return this.userLoginRedisClient.del(item.token);
    });
    await Promise.all(delTokensPromises);
  }

  // hash密码
  private sha256Password(pwd: string): string {
    const hmac = createHmac('sha256', 'huihifi*&%&*(&%');
    hmac.update(pwd);
    return hmac.digest('base64');
  }

  async openapiLoginCheck(body: UserOpenApiLoginCheckDto) {
    const userStr = await this.userLoginRedisClient.get(body.token);
    if(!userStr){
      throw new BizFailedException(BizFailedCodeEnum.USER_LOGIN_WRONG);
    }
    const user = JSON.parse(userStr);
    const res = await this.userModelRepository.findOne({
      where: {
        uuid: user.uuid,
      },
      select: [
        'uuid',
        'name',
        'mobile',
        'mobile',
        'title',
        'userType',
        'avatar',
        'titleCertification',
        'signature',
        'createTime'
      ],
    });
    if(!res || res.status === BooleanNumber.false){
      throw new BizFailedException(BizFailedCodeEnum.USER_DISABLED);
    }
    return res;
  }
}
